package java_thinking.unit_21._21_12;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;

import java.util.concurrent.Semaphore;

/**
 * @Desc:   12-1 基于型号量的有界缓存测试
 *              1 在实际情况中，如果需要一个有界缓存，应该直接使用 ArrayBlockingQueue 或 LinkedBlockingQueue，
 *                而不是自己编写。
 *              2 测试实例见 SemaphoreBoundedBufferTest.java
 * @author: cww
 * @DateTime: 2019-06-23 11:27
 */
@ThreadSafe
public class SemaphoreBoundedBuffer<E> {
    /**
     * availableItem 表示可以从缓存中删除的元素个数，初始值为零，因为缓存的初始状态为空
     * availableSpace 表示可以插入到缓存的元素个数，它的初始值等于缓存大小
     */
    private final Semaphore availableItem, availableSpace;
    /**
     * 缓存
     */
    @GuardedBy("this") private final E[] item;
    @GuardedBy("this") private int putPosition = 0, takePosition = 0;

    public SemaphoreBoundedBuffer(int capacity) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.availableItem = new Semaphore(0);
        this.availableSpace = new Semaphore(capacity);
        this.item = (E[]) new Object[capacity];
    }
    public boolean isEmpty() {
        return availableItem.availablePermits() == 0;
    }
    public boolean isFull() {
        return availableSpace.availablePermits() == 0;
    }
    public void put(E x) throws InterruptedException {
        //获得一个插入许可，阻塞直到有空或者线程中断
        availableSpace.acquire();
        doInsert(x);
        //释放许可，表示缓存不为空，不为空执行 acquire 将返回通过许可
        availableItem.release();
    }

    public E take() throws InterruptedException {
        //从 availableItem 获得一个许可，如果缓存不为空，这个请求立即成功，否则被阻塞直到缓存不为空
        //所以availableItems的信号量会变小
        E item = null;
        try {
            availableItem.acquire();
            //删除缓存中的下一个元素
            item = doExtract();
            //返回一个许可信号量到 availableSpace 信号量表示可以多插入一个缓存了
            availableSpace.release();
        } catch (Exception e) {
            throw new InterruptedException("Failed by take() error");
        }
        return item;
    }

    public synchronized void doInsert(E x) {
        int i = putPosition;
        item[i] = x;
        //如果++后等于队列长度，此时items[i]越界，所以返回0，否则返回i
        putPosition = (++i == item.length)? 0 : i;
    }

    public synchronized E doExtract() {
        int i = takePosition;
        E x = item[i];
        item[i] = null;
        takePosition = (++i == item.length)? 0 : i;
        return x;
    }

}
